const fs = require('fs-extra');
const path = require('path');
const middleware = require('../middleware');
const PhoenixError = require('../../models/phoenix-error');
const uploadProgress = require('../middleware/upload-progress');
const codeInstrumentationsModels = require('../../models/code-instrumentation');
const files = require('../../models/files');
const tasks = require('../../models/tasks');
const utility = require('../../models/utility');
const logger = require('../../models/logger');

function createCodeInstrumentation(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const {
      fileIds = [],
      functionIds = [],
      description = '插桩源代码',
      instrumentWay = undefined,
      output = undefined,
    } = req.body;
    if (!fileIds.length && !functionIds.length) {
      throw new PhoenixError('invalid request', 'need files or functions');
    }
    const ret = await codeInstrumentationsModels.createCodeInstrumentation(
      projectVersionId,
      fileIds,
      functionIds,
      description,
      codeInstrumentationsModels.type.codeInstrumentation,
      true,
      instrumentWay,
      output
    );
    const task = await tasks.addInstrumentTask(ret._id, ret.files, req.$project, req.$projectVersion, req.$me);
    res.json({
      codeInstrumentationId: ret._id,
      name: ret.number,
      task: tasks.getJson(task),
    });
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function codeInstrumentationList(req, res, next) {
  (async () => {
    const results = await codeInstrumentationsModels.codeInstrumentationList(
      req.$project,
      req.$projectVersion
    );
    res.json(results);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function codeInstrumentationEdit(req, res, next) {
  (async () => {
    const codeIns = req.body;
    if (!codeIns || codeIns.length === 0) {
      res.json();
      return;
    }
    await codeInstrumentationsModels.codeInstrumentationEdit(codeIns, req.$codeInstrumentation);
    res.json();
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getCodeInstrumentationTree(req, res, next) {
  (async () => {
    const tree = await codeInstrumentationsModels.getCodeInstrumentationTree(req.$project, req.$codeInstrumentation);
    res.json(tree);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getFileContent(req, res, next) {
  (async () => {
    const { filePath, codeInstrumentationId } = req.query;
    const { projectVersionId } = req.params;
    if (!filePath) {
      throw new PhoenixError('invalid request', 'need filePath !');
    }
    const file = await files.getFileByPath(projectVersionId, filePath);
    if (!file) {
      throw new Error(`file:${filePath} not exist`);
    }
    const encoding = file.encoding || req.$projectVersion.encoding || 'uft-8';
    res.set({
      'Content-Type': `text/plain; charset=${encoding}`,
    });
    const codeInstrumentationFilePath = await codeInstrumentationsModels.getFileCodeInstrumentationFilePath(
      projectVersionId,
      filePath,
      codeInstrumentationId
    );
    if (!codeInstrumentationFilePath
      || !(await utility.doesFileExist(codeInstrumentationFilePath))
    ) {
      logger.debug('code instrumentation file not exist');
      res.write('');
      res.end();
    } else {
      const stream = fs.createReadStream(codeInstrumentationFilePath);
      stream.on('data', (chunk) => {
        res.write(chunk);
      });
      stream.on('end', () => res.end());
      stream.on('error', (err) => {
        logger.debug(`read code instrumentation file err:${err}`);
        res.write('');
        res.end();
      });
    }
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function getFilesAndFunctions(req, res, next) {
  (async () => {
    const { projectVersionId } = req.params;
    const { codeInstrumentationId } = req.query;
    const ids = await codeInstrumentationsModels.getFilesAndFunctions(projectVersionId, codeInstrumentationId);
    res.json({ ids });
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function uploadResultFile(req, res, next) {
  (async () => {
    if (!req.file) {
      throw new PhoenixError('invalid request', 'Missing parameters. "file" is required');
    }
    const { codeInstrumentationId } = req.params;
    if (req.$codeInstrumentation.status !== codeInstrumentationsModels.statusKind.finish) {
      throw new PhoenixError('code instrumentation frozen', `codeInstrumentation:${codeInstrumentationId} frozen,forbidden upload results file`);
    }
    const fileExtName = path.extname(req.file.originalname);

    if (fileExtName !== '.zip') {
      throw new PhoenixError('invalid request', 'The file is not a zip file');
    }
    const results = await codeInstrumentationsModels.uploadResultFile(
      codeInstrumentationId,
      req.file.path,
      req.$projectVersion,
      req.$project,
      req.$me
    );
    res.json(results);
  })().catch((err) => {
    next(new PhoenixError(err));
  });
}
function removeCodeInstrumentation(req, res, next) {
  (async () => {
    await codeInstrumentationsModels.removeCodeInstrumentation(req.$codeInstrumentation);
    res.status(204).json();
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}
function removeCodeInstrumentationResult(req, res, next) {
  (async () => {
    if (utility.isArrayType(req.body)) {
      await Promise.all(req.body.map(({ codeInstrumentationId, resultIds }) => {
        if (!codeInstrumentationId || !utility.isArrayType(resultIds)) {
          return Promise.resolve();
        }
        return codeInstrumentationsModels.removeCodeInstrumentationResult(codeInstrumentationId, resultIds || [], req.$projectVersion);
      }));
    }
    res.status(204).json();
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}
function getAnalyzedFileTree(req, res, next) {
  (async () => {
    const tree = await codeInstrumentationsModels.getAnalyzedFileTree(req.body, req.$projectVersion);
    res.json(tree);
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}
function importAnalyzedResults(req, res, next) {
  (async () => {
    await codeInstrumentationsModels.importAnalyzedResults(req.body, req.$projectVersion);
    res.status(204).json();
  })()
    .catch((err) => {
      next(new PhoenixError(err));
    });
}

module.exports = (app, prefix) => {
  const createCodeInstrumentationURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation.json`;
  const codeInstrumentationURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation/list.json`;
  const codeInstrumentationEditURL = `${prefix}/code-instrumentation/:codeInstrumentationId.json`;
  const codeInstrumentationTreeURL = `${prefix}/code-instrumentation/:codeInstrumentationId/tree.json`;
  const codeInstrumentationAnalyzeURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation-analyze`;
  const fileContentURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation/file/content`;
  const filesFunctionsURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation/files-functions.json`;
  const uploadResultsFileURL = `${prefix}/code-instrumentation/:codeInstrumentationId/results/upload.json`;
  const removeResultsURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation-results`;
  const fileTreeURL = `${prefix}/project-versions/:projectVersionId/code-instrumentation-results/tree-all.json`;
  const upload = uploadProgress;

  // 创建插桩代码
  app.post(createCodeInstrumentationURL, middleware.user.requireTokenUnexpired);
  app.post(createCodeInstrumentationURL, middleware.user.requireAvailable);
  app.post(createCodeInstrumentationURL, middleware.user.requireTester);
  app.post(createCodeInstrumentationURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(createCodeInstrumentationURL, createCodeInstrumentation);

  // 获取插桩代码列表
  app.get(codeInstrumentationURL, middleware.user.requireTokenUnexpired);
  app.get(codeInstrumentationURL, middleware.user.requireAvailable);
  app.get(codeInstrumentationURL, middleware.user.requireTester);
  app.get(codeInstrumentationURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(codeInstrumentationURL, codeInstrumentationList);
  // 编辑插桩代码信息
  app.put(codeInstrumentationEditURL, middleware.user.requireTokenUnexpired);
  app.put(codeInstrumentationEditURL, middleware.user.requireAvailable);
  app.put(codeInstrumentationEditURL, middleware.user.requireTester);
  app.put(codeInstrumentationEditURL, middleware.codeInstrumentation.requireCodeInstrumentationExist);
  app.put(codeInstrumentationEditURL, codeInstrumentationEdit);
  // 删除插桩代码信息
  app.delete(codeInstrumentationEditURL, middleware.user.requireTokenUnexpired);
  app.delete(codeInstrumentationEditURL, middleware.user.requireAvailable);
  app.delete(codeInstrumentationEditURL, middleware.user.requireTester);
  app.delete(codeInstrumentationEditURL, middleware.codeInstrumentation.requireCodeInstrumentationExist);
  app.delete(codeInstrumentationEditURL, removeCodeInstrumentation);
  // 删除插桩代码信息
  app.delete(removeResultsURL, middleware.user.requireTokenUnexpired);
  app.delete(removeResultsURL, middleware.user.requireAvailable);
  app.delete(removeResultsURL, middleware.user.requireTester);
  app.delete(removeResultsURL, middleware.projectVersion.requireProjectVersionExist);
  app.delete(removeResultsURL, removeCodeInstrumentationResult);
  // 获取插桩代码插桩文件函数树
  app.get(codeInstrumentationTreeURL, middleware.user.requireTokenUnexpired);
  app.get(codeInstrumentationTreeURL, middleware.user.requireAvailable);
  app.get(codeInstrumentationTreeURL, middleware.user.requireTester);
  app.get(codeInstrumentationTreeURL, middleware.codeInstrumentation.requireCodeInstrumentationExist);
  app.get(codeInstrumentationTreeURL, getCodeInstrumentationTree);
  // 获取插桩后文件插桩源码内容
  app.post(fileContentURL, middleware.user.requireTokenUnexpired);
  app.post(fileContentURL, middleware.user.requireAvailable);
  app.post(fileContentURL, middleware.user.requireTester);
  app.post(fileContentURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(fileContentURL, getFileContent);
  // 获取插桩文件和函数
  app.get(filesFunctionsURL, middleware.user.requireTokenUnexpired);
  app.get(filesFunctionsURL, middleware.user.requireAvailable);
  app.get(filesFunctionsURL, middleware.user.requireTester);
  app.get(filesFunctionsURL, middleware.projectVersion.requireProjectVersionExist);
  app.get(filesFunctionsURL, getFilesAndFunctions);
  // 上传结果文件
  app.post(uploadResultsFileURL, middleware.user.requireTokenUnexpired);
  app.post(uploadResultsFileURL, middleware.user.requireAvailable);
  app.post(uploadResultsFileURL, middleware.user.requireTester);
  app.post(uploadResultsFileURL, middleware.codeInstrumentation.requireCodeInstrumentationExist);
  app.post(uploadResultsFileURL, middleware.progressNotify());
  app.post(uploadResultsFileURL, upload.single('file'));
  app.post(uploadResultsFileURL, uploadResultFile);
  // 获取结果文件树
  app.post(fileTreeURL, middleware.user.requireTokenUnexpired);
  app.post(fileTreeURL, middleware.user.requireAvailable);
  app.post(fileTreeURL, middleware.user.requireTester);
  app.post(fileTreeURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(fileTreeURL, getAnalyzedFileTree);
  // 插桩结果分析导入当前版本中
  app.post(codeInstrumentationAnalyzeURL, middleware.user.requireTokenUnexpired);
  app.post(codeInstrumentationAnalyzeURL, middleware.user.requireAvailable);
  app.post(codeInstrumentationAnalyzeURL, middleware.user.requireTester);
  app.post(codeInstrumentationAnalyzeURL, middleware.projectVersion.requireProjectVersionExist);
  app.post(codeInstrumentationAnalyzeURL, importAnalyzedResults);
};
